package com.conop.gradient;

/**
 * N-Dimensional Point
 * @author Alex Spivakovsky
 * @version 08.07.2012
 */
public class NDPoint
{
	private double[] _values;
	private final int _dim;
	
	/**
	 * Construction of N dimensional point from an array of double values
	 * @param pnt <b>-</b> An array of double values
	 */
	public NDPoint(final double[] pnt)
	{
		_dim = pnt.length;
		_values = new double[_dim];
		
		Parallel.For(0, _dim, new LoopBody<Integer>() {
			
			@Override
			public void run(Integer i) {
				_values[i] = pnt[i];	
			}
		});
	}
	
	/**
	 * Copy c'tor
	 * @param pnt <b>-</b> a NDPoint to create a copy of
	 */
	public NDPoint(NDPoint pnt)
	{
		this(pnt._values);
	}
	
	/**
	 * Create an NDPoint which is a repetition of <code>value</code>, <code>rep</code> times
	 * @param value - The value we wish to initialize the point with
	 * @param rep - The dimension of the point
	 * @throws IllegalArgumentException If <code>rep</code> is smaller than 1
	 */
	public NDPoint(final double value, int rep) throws IllegalArgumentException
	{	
		if (rep <= 0)
			throw new IllegalArgumentException("Dimension must be a values larger than 1");
		
		_dim = rep;
		_values = new double[_dim];
		
		Parallel.For(0, _dim, new LoopBody<Integer>() {
			
			@Override
			public void run(Integer i) {
				_values[i] = value;
				
			}
		});
	}

	/**
	 * Sums two points and return a new NDPoint of the result
	 * @param pnt - The point we wish to add
	 * @return NDPoint which is the result of the addition process
	 * @throws IllegalArgumentException If the dimensions of the two points don't agree
	 */
	public NDPoint Addition(final NDPoint pnt) throws IllegalArgumentException
	{
		if (pnt._dim != this._dim)
			throw new IllegalArgumentException("NDPoint dimensions must agree");
		
		final NDPoint new_pnt = new NDPoint(this);
		
		Parallel.For(0, _dim, new LoopBody<Integer>() {
			
			@Override
			public void run(Integer i) {
				new_pnt._values[i] += pnt._values[i];
				
			}
		});
		
		return new_pnt;
	}

	/**
	 * Returns a new point which is the current point multiplied by <code>d</code>
	 * @param d - The factor of multiplication
	 * @return A new NDPoint which is the result of the current point multiplied by <code>d</code>
	 */
	public NDPoint Multiply(final double d)
	{
		final NDPoint pnt = new NDPoint(this);
		
		Parallel.For(0, _dim, new LoopBody<Integer>() {
			
			@Override
			public void run(Integer i) {
				pnt._values[i] *= d;
				
			}
		});
		
		return pnt;
	}
	
	/**
	 * Substracts two points and return a new NDPoint of the result
	 * @param pnt - The point we wish to substract
	 * @return NDPoint which is the result of the substraction process
	 * @throws IllegalArgumentException If the dimensions of the two points don't agree
	 */
	public NDPoint Substraction(NDPoint pnt) throws IllegalArgumentException
	{
		return this.Addition(pnt.Multiply(-1.0));
	}

	/**
	 * Compute the dot product (a'*b) of two NDPoints
	 * @param pnt - The point we wish to compute the inner product with
	 * @return The result of the inner product between two NDPoints
	 * @throws IllegalArgumentException If the dimensions of the two points don't agree
	 */
	public double DotProduct(final NDPoint pnt) throws IllegalArgumentException
	{
		if (pnt._dim != this._dim)
			throw new IllegalArgumentException("NDPoint dimensions must agree");
		
		final double[] return_value = {0.0};
		
		Parallel.For(0, _dim, new LoopBody<Integer>() {
			
			@Override
			public void run(Integer i) {
				return_value[0] +=  _values[i] * pnt._values[i];
				
			}
		});
		
		return return_value[0];	
	}
	
	/**
	 * Compute ||x|| = sqrt(sum(x_i^2))
	 * @return The Eculedien norm of the NDPoint
	 */
	public double Norm()
	{	
		return Math.sqrt(this.DotProduct(this));
	}
	
	/**
	 * The dimension of the point
	 * @return The dimension of the point
	 */
	public int getDim()
	{
		return _dim;
	}
	
	/**
	 * Get the value of the ith coordinate
	 * @param i - Then index of the coordinate we wish to retrieve
	 * @return The ith double coordinate
	 * @throws IllegalArgumentException If the index is out of bounds
	 */
	double getCoordinate(int i) throws IllegalArgumentException
	{
		if (i < 0 || i >= _dim)
			throw new IllegalArgumentException("Coordinate index can't be negative or bigger than the dimension");
		
		return _values[i];
	}
	
	/**
	 * Checks if two NDPoints are identical elementwise
	 */
	@Override
	public boolean equals(Object obj)
	{
		if (!(obj instanceof NDPoint))
			return false;
		
		NDPoint pnt = (NDPoint) obj;
		
		for (int i = 0; i < pnt._dim; i++)
			if (this._values[i] != pnt._values[i])
				return false;
		
		return true;
	}
	
	@Override
	public int hashCode()
	{
		assert false : "hashCode not  designed";
		return 42;
	}

	@Override
	public String toString()
	{
		
		StringBuilder str = new 
				StringBuilder(Integer.toString(_dim)).append("D Point: (");
		
		for (double d : _values)
		{
			str.append(String.format(" %f", d));
		}
		
		return (str.append(" )")).toString();
	}
}
